Audio: Buffers: Add support for DP-to-DP component binding - #10562
Audio: Buffers: Add support for DP-to-DP component binding#10562singalsu wants to merge 1 commit into
Conversation
|
No, no, no! No |
Would be great if that can be done! I need this to have decoder and phase_vocoder in same pipeline without LL components in between throttling the playback data. The phase vocoder consumes at input the audio data at 0.5x to 2.0x speed vs. normal. There can't be LL components with speed higher than 1.0. |
@softwarecki whats your rough schedule for dp-dp ? It seems like we have a real use case now that needs it. |
46ecff9 to
cdc456a
Compare
There was a problem hiding this comment.
Pull request overview
This PR extends the IPC4 binding and buffering infrastructure to allow binding two DP-scheduled components together by introducing a dual-ring-buffer “hybrid” topology around an intermediate comp_buffer, enabling lock-free DP access on both sides while preserving LL-cycle synchronization semantics.
Changes:
- Allow DP→DP binds in
ipc_comp_connect()and create/attach a secondring_bufferfor the source side in the DP→DP case. - Update secondary-buffer attachment and syncing logic to permit and handle both
secondary_buffer_sinkandsecondary_buffer_sourcesimultaneously. - Add vregion refcount release in
ring_buffer_free()to match new vregion refcounting during ring buffer creation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/ipc/ipc4/helper.c | Removes DP→DP bind rejection and adds dual ring-buffer creation/attachment for DP→DP connections. |
| src/audio/buffers/audio_buffer.c | Allows per-side secondary attachments and adds a dual-secondary sync path for DP→DP cascaded copying. |
| src/audio/buffers/ring_buffer.c | Releases vregion references during ring buffer free to match new vregion refcounting behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
See thesofproject#10562 Previously binding two DP (Data Processing) scheduled components was rejected with IPC4_INVALID_REQUEST. This patch adds support for DP-to-DP binding by creating a dual ring buffer configuration where each DP module gets its own ring buffer on either side of the intermediate comp_buffer. Data flow for DP-to-DP: src_DP -> ring_buf_src -> comp_buffer -> ring_buf_sink -> sink_DP Changes in helper.c: - Remove the DP-to-DP bind rejection in ipc_comp_connect(). - Add src_is_dp, sink_is_dp, and dp_to_dp flags to detect the DP-to-DP case. - Create a second ring_buffer allocated from the source module's mod_alloc_ctx for the source side of the comp_buffer. - Refcount the DP vregion for each created ring_buffer via vregion_get(), with a NULL alloc guard. Changes in audio_buffer.c: - Change audio_buffer_attach_secondary_buffer() from a global rejection to per-side checks, allowing both secondary_buffer_sink and secondary_buffer_source to be set simultaneously. - Add a dual-secondary sync path in audio_buffer_sync_secondary_buffer() that cascades data through: input ring_buffer -> comp_buffer -> output ring_buffer, with rate-limiting applied on the output side. Changes in ring_buffer.c: - Release the DP vregion in ring_buffer_free() via vregion_put() and free the mod_alloc_ctx when the refcount reaches zero, matching the pattern used in comp_buffer_free(). Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
|
@softwarecki @abonislawski this is needed very soon, do you have an ETA or can we go ahead here in the short term. |
cdc456a to
70ff3f1
Compare
|
I just tested this updated patch with #11092, and it worked OK. |
70ff3f1 to
0570d14
Compare
0570d14 to
58d6216
Compare
58d6216 to
7c41c51
Compare
kv2019i
left a comment
There was a problem hiding this comment.
Looks clean. A few questions inline. There are also tests failing, can you take a look at those.
| int audio_buffer_attach_secondary_buffer(struct sof_audio_buffer *buffer, bool at_input, | ||
| struct sof_audio_buffer *secondary_buffer) | ||
| { | ||
| #if CONFIG_DP_TO_DP_BIND |
There was a problem hiding this comment.
Style note, "#ifdef CONFIG_DP_TO_DP_BIND" is the usual convention. @lyakh agrees, but Linux kernel and statistics of use in SOF are on my side with this.
There was a problem hiding this comment.
do I prefer #if? I don't know any more :-D I don't care that much really. I thought one was preferred and I tried to comply, but then we didn't find any written down preference for SOF. My current dilemma is on the one hand #if is shorter and is easier to extend with logical operations like #if CONFIG_A || CONFIG_B, but OTOH #ifdef is "cleaner" because when something isn't defined, it shouldn't really be possible to check its value...
| * ring_buffer_free for DP-to-DP binding) | ||
| */ | ||
| if (ring_buffer->audio_buffer.alloc) | ||
| vregion_get(ring_buffer->audio_buffer.alloc->vreg); |
There was a problem hiding this comment.
This I don't fully get. Why do we need an additional vregion_get/put on the ringbuffer that we already allocated in the normal single DP case. This seems correct, but I'm puzzled why this ref is not taken in ring_buffer_create(). @lyakh any thoughts?
There was a problem hiding this comment.
vregion reference counting was added because while we are allocating components and their data in and around the module-adapter, which is also where the vregion is created in the first place, we also create "normal" component buffers on that vregion. And while creation is done during component instantiation, which happens first, during freeing one of the buffers happens to be freed last - after the component. So, with ring buffers it wasn't needed until now because they are never created first or freed last. On the one hand refcounting them doesn't hurt (if done correctly) and might seem logical, OTOH if it isn't really needed - why add it.
There was a problem hiding this comment.
Hmm, thanks @lyakh . Makes sense, but I still think without above addennum, the code is hard to understand. I we need further updates, I'd add some comment about this.
| MAX(obs, src_module_data->mpd.out_buff_size), | ||
| is_shared, buf_id); | ||
| if (!ring_buffer2) { | ||
| buffer_free(buffer); |
There was a problem hiding this comment.
How about the first ring_buffer we allocated (and the vregion ref), should we free those here?
| to_copy = MIN(MIN(data_available, free_size), limit); | ||
|
|
||
| err = source_to_sink_copy(data_src, data_dst, true, to_copy); | ||
| return err; |
There was a problem hiding this comment.
return source_to_sink_copy(data_src, data_dst, true, to_copy);
| if (buffer->secondary_buffer_sink && buffer->secondary_buffer_source) { | ||
| /* | ||
| * DP-to-DP case: both secondary buffers present. | ||
| * Data flows: input_ring_buffer -> comp_buffer -> output_ring_buffer |
There was a problem hiding this comment.
I'm wondering... I think the ring buffer was designed in a way to support asynchronous / lockless reading and writing (or something similar) and it should have been tailored to the use with DP. Shouldn't it be possible to just do DP -> ring_buffer -> DP?
There was a problem hiding this comment.
Yes, only one ring buffer should be used here.
| if (alloc && alloc->vreg) { | ||
| if (!vregion_put(alloc->vreg)) | ||
| rfree(alloc); | ||
| } |
There was a problem hiding this comment.
if (alloc && alloc->vreg && !vregion_put(alloc->vreg))
| int audio_buffer_attach_secondary_buffer(struct sof_audio_buffer *buffer, bool at_input, | ||
| struct sof_audio_buffer *secondary_buffer) | ||
| { | ||
| #if CONFIG_DP_TO_DP_BIND |
There was a problem hiding this comment.
do I prefer #if? I don't know any more :-D I don't care that much really. I thought one was preferred and I tried to comply, but then we didn't find any written down preference for SOF. My current dilemma is on the one hand #if is shorter and is easier to extend with logical operations like #if CONFIG_A || CONFIG_B, but OTOH #ifdef is "cleaner" because when something isn't defined, it shouldn't really be possible to check its value...
| * ring_buffer_free for DP-to-DP binding) | ||
| */ | ||
| if (ring_buffer->audio_buffer.alloc) | ||
| vregion_get(ring_buffer->audio_buffer.alloc->vreg); |
There was a problem hiding this comment.
vregion reference counting was added because while we are allocating components and their data in and around the module-adapter, which is also where the vregion is created in the first place, we also create "normal" component buffers on that vregion. And while creation is done during component instantiation, which happens first, during freeing one of the buffers happens to be freed last - after the component. So, with ring buffers it wasn't needed until now because they are never created first or freed last. On the one hand refcounting them doesn't hurt (if done correctly) and might seem logical, OTOH if it isn't really needed - why add it.
PR 10562: test resultsRun date: 2026-09-09 08:01 UTC Tested commit: 053828bab86fbc6b4985c7f2f921af72c5eff1d5 |
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core IPC binding and cross-domain buffer synchronization/lifetime semantics, which can impact correctness across cores and scheduling domains.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
| #if CONFIG_DP_TO_DP_BIND | ||
| /* | ||
| * For DP-to-DP binding: matches vregion_get() in ipc_comp_connect() | ||
| * for each ring_buffer. Releases the DP module's virtual memory region | ||
| * and frees the module allocation context when the refcount reaches zero. | ||
| */ | ||
| if (alloc && alloc->vreg) { | ||
| if (!vregion_put(alloc->vreg)) | ||
| rfree(alloc); | ||
| } |
| #if CONFIG_DP_TO_DP_BIND | ||
| /* refcount the DP vregion for this ring_buffer (matches vregion_put in | ||
| * ring_buffer_free for DP-to-DP binding) | ||
| */ | ||
| if (ring_buffer->audio_buffer.alloc) | ||
| vregion_get(ring_buffer->audio_buffer.alloc->vreg); | ||
| #endif |
lyakh
left a comment
There was a problem hiding this comment.
let's clarify whether the ring -> comp -> ring buffer concept is acceptable or whether we want a better one from the beginning
7c41c51 to
6ceffcb
Compare
It's now rebased. No other changes. |
6ceffcb to
229c19d
Compare
|
Note: New version with single ring buffer. It worked in my DP-DP topologies tests (phase vocoder, MFCC Whisper ASR offload), so I'm proposing this new version now. |
There was a problem hiding this comment.
🟡 Changes recommended
There is a confirmed allocator-free mismatch in ring_buffer_free() and the dual-secondary synchronization/DP-to-DP design is inconsistent with the PR’s stated dataflow, risking incorrect runtime behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Lite
Binding two DP (Data Processing) scheduled components was previously rejected with IPC4_INVALID_REQUEST because both sides required a secondary ring buffer. This patch adds support for DP-to-DP component binding under a new CONFIG_DP_TO_DP_BIND Kconfig option. In a DP-to-DP connection, a single shared ring buffer is created and attached as a secondary buffer on both the source and sink sides of the intermediate comp_buffer. The upstream DP module writes directly to the ring buffer sink API, and the downstream DP module reads directly from its source API. No copying or intermediate synchronization is required during low-latency (LL) scheduling cycles. The DP module virtual memory region backing the ring buffer is refcounted so that it remains valid across component lifetimes, and audio buffer reset and free operations ensure the shared secondary buffer is not reset or freed twice. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
229c19d to
053828b
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
The current secondary-buffer sync logic does not correctly support the dual-secondary-buffer scenario enabled by the new attachment rules, and the DP→DP implementation diverges from the PR’s described dual-ring-buffer design.
Review details
Suppressed comments (2)
src/audio/buffers/audio_buffer.c:76
- audio_buffer_sync_secondary_buffer() prioritizes secondary_buffer_sink whenever it is set, so if both secondary_buffer_sink and secondary_buffer_source are set (the new attach behavior under CONFIG_DP_TO_DP_BIND allows this), the output-side sync is skipped. This makes the dual-secondary-buffer configuration effectively unsupported and can stall data on the output side.
if (buffer->secondary_buffer_sink) {
/*
* audio_buffer sink API is shadowed, that means there's a secondary_buffer
* at data input
* get data from secondary_buffer (use source API)
src/ipc/ipc4/helper.c:985
- The DP-to-DP path implemented here attaches the same ring_buffer on both sides of the comp_buffer (and audio_buffer_sync_secondary_buffer() returns early when both secondary pointers are equal), which effectively bypasses the intermediate comp_buffer. This does not match the PR description that calls for two ring buffers (one per DP module heap) on either side of the comp_buffer; please align the implementation and/or the PR description (including the documented data-flow).
#ifdef CONFIG_DP_TO_DP_BIND
if (dp_to_dp) {
/*
* DP-to-DP binding: both source and sink are DP modules.
* A single shared ring_buffer is attached on both sides
* of the comp_buffer, so source DP writes directly to it
* and sink DP reads directly from it without copying.
*/
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, true,
&ring_buffer->audio_buffer);
audio_buffer_attach_secondary_buffer(&buffer->audio_buffer, false,
&ring_buffer->audio_buffer);
} else
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
| struct sof_audio_buffer *secondary_buffer) | ||
| { | ||
| #ifdef CONFIG_DP_TO_DP_BIND | ||
| /* check per-side: allow attaching on both sides (needed for DP-to-DP) */ |
There was a problem hiding this comment.
is this the case of attaching the same buffer for the second time after one side has already been attached? Maybe rephrase the comment a bit to clarify that
| audio_buffer_free(buffer->secondary_buffer_sink); | ||
| #ifdef CONFIG_DP_TO_DP_BIND | ||
| if (buffer->secondary_buffer_source != buffer->secondary_buffer_sink) | ||
| audio_buffer_free(buffer->secondary_buffer_source); |
There was a problem hiding this comment.
but will it be freed eventually on the second call?
| if (buffer->secondary_buffer_source && | ||
| buffer->secondary_buffer_source != buffer->secondary_buffer_sink && | ||
| buffer->secondary_buffer_source->ops->reset) | ||
| buffer->secondary_buffer_source->ops->reset(buffer->secondary_buffer_source); |
There was a problem hiding this comment.
also here - will it ever be reset or is it intended that it never gets reset?
| else if (src_is_dp) | ||
| dp = source; | ||
| else | ||
| dp = NULL; |
There was a problem hiding this comment.
this is now interesting. Previously buffers attached to DP modules were allocated on that DP module's vregion to have them accessible from that memory domain. How would this be resolved now? Do both DP modules on the two sides of the ring buffer have to belong to the same memory domain?..
Previously binding two DP (Data Processing) scheduled components was rejected with IPC4_INVALID_REQUEST. This patch adds support for DP-to-DP binding by creating a dual ring buffer configuration where each DP module gets its own ring buffer on either side of the intermediate comp_buffer.
Data flow for DP-to-DP:
src_DP -> ring_buf_src -> comp_buffer -> ring_buf_sink -> sink_DP
Changes in helper.c:
Changes in audio_buffer.c:
that cascades data through: input ring_buffer -> comp_buffer ->
output ring_buffer, with rate-limiting applied on the output side.
Changes in ring_buffer.c: